package org.dylan.security.service;

import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.crypto.hash.format.ParsableHashFormat;
import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat;
import org.apache.shiro.util.ByteSource;

/**
 * 密码服务类
 * 
 * @author dylan
 * 
 */
public class PasswordService {

	public static final String DEFAULT_HASH_ALGORITHM = "SHA-256";
	public static final int DEFAULT_HASH_ITERATIONS = 500000; // 500,000
	public static final boolean DEFAULT_GENERATE_PUBLIC_SLAT = true;
	protected DefaultHashService hashService;
	protected ParsableHashFormat hashFormat;

	public PasswordService() {
		hashService = new DefaultHashService();
		hashService.setHashAlgorithmName(DEFAULT_HASH_ALGORITHM);
		hashService.setHashIterations(DEFAULT_HASH_ITERATIONS);
		hashService.setGeneratePublicSalt(DEFAULT_GENERATE_PUBLIC_SLAT);
		hashFormat = new Shiro1CryptFormat();
	}

	/**
	 * 对普通字符串进行加密操作
	 * 
	 * @param plaintextPassword
	 * @return
	 */
	public String encryptPassword(Object plaintextPassword) {
		Hash hash = hashPassword(plaintextPassword);
		return this.hashFormat.format(hash);
	}

	/**
	 * 将普通字符串加密为Hash对象
	 * 
	 * @param plaintext
	 * @return
	 */
	public Hash hashPassword(Object plaintext) {
		ByteSource plaintextBytes = createByteSource(plaintext);
		if (plaintextBytes == null || plaintextBytes.isEmpty()) {
			return null;
		}
		HashRequest request = createHashRequest(plaintextBytes);
		return hashService.computeHash(request);
	}

	protected HashRequest createHashRequest(ByteSource plaintext) {
		return new HashRequest.Builder().setSource(plaintext).build();
	}

	protected ByteSource createByteSource(Object o) {
		return ByteSource.Util.bytes(o);
	}

	/**
	 * 检查原始文本与加密之后的内容是否匹配
	 * 
	 * @param submittedPlaintext
	 * @param encrypted
	 * @return
	 */
	public boolean passwordsMatch(Object submittedPlaintext, String encrypted) {
		ByteSource plaintextBytes = createByteSource(submittedPlaintext);

		if (encrypted == null || encrypted.length() == 0) {
			return plaintextBytes == null || plaintextBytes.isEmpty();
		} else {
			if (plaintextBytes == null || plaintextBytes.isEmpty()) {
				return false;
			}
		}
		Hash savedHash = parseHashedPassword(encrypted);

		return passwordsMatch(submittedPlaintext, savedHash);
	}

	/**
	 * 从加密串中解析出Hash对象，得到其加密算法，迭代次数等信息
	 * 
	 * @param encrypted
	 * @return
	 */
	public Hash parseHashedPassword(String encrypted) {
		return hashFormat.parse(encrypted);
	}

	public boolean passwordsMatch(Object plaintext, Hash saved) {
		ByteSource plaintextBytes = createByteSource(plaintext);

		if (saved == null || saved.isEmpty()) {
			return plaintextBytes == null || plaintextBytes.isEmpty();
		} else {
			if (plaintextBytes == null || plaintextBytes.isEmpty()) {
				return false;
			}
		}

		HashRequest request = buildHashRequest(plaintextBytes, saved);

		Hash computed = this.hashService.computeHash(request);

		return saved.equals(computed);
	}

	protected HashRequest buildHashRequest(ByteSource plaintext, Hash saved) {
		// keep everything from the saved hash except for the source:
		return new HashRequest.Builder()
				.setSource(plaintext)
				// now use the existing saved data:
				.setAlgorithmName(saved.getAlgorithmName())
				.setSalt(saved.getSalt()).setIterations(saved.getIterations())
				.build();
	}

	public String getAlgorithmName() {
		return hashService.getHashAlgorithmName();
	}

	public int getHashIterations() {
		return hashService.getHashIterations();
	}

	public boolean isGeneratePublicSalt() {
		return hashService.isGeneratePublicSalt();
	}

	public void setGeneratePublicSalt(boolean generatePublicSalt) {
		hashService.setGeneratePublicSalt(generatePublicSalt);
	}

	public void setAlgorithmName(String algorithmName) {
		hashService.setHashAlgorithmName(algorithmName);
	}

	public void setHashIterations(int hashIterations) {
		hashService.setHashIterations(hashIterations);
	}

}
